﻿using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualBasic;
using Service.Core.EfCoreRepository.CAP;
using Service.Core.EfCoreRepository.Core;
using Service.Core.EfCoreRepository.Extension;
using Service.Core.EfCoreRepository.Interfaces;
using Service.Framework.Utils;
using System.Diagnostics;
using System.Reflection;

namespace Service.Core.EfCoreRepository
{
    public abstract class AppDbContextBase : DbContext, ITransactionUnitOfWork
    {

        private readonly IMediator _mediator;
        private readonly IHttpContextAccessor _httpContextAccessor;
        readonly IPublisherTransactionHandler? _publisherTransactionFactory;

        protected AppDbContextBase(DbContextOptions options, IMediator mediator, IServiceProvider provider, IHttpContextAccessor httpContextAccessor) :
            base(options)
        {
            _mediator = mediator;
            _publisherTransactionFactory = provider.GetService<IPublisherTransactionHandler>();
            _httpContextAccessor = httpContextAccessor;
        }


        protected virtual void ConfigureStronglyTypedIdValueConverter(ModelConfigurationBuilder configurationBuilder)
        {
        }

        protected virtual void ConfigureRowVersion(ModelBuilder modelBuilder)
        {
            ArgumentNullException.ThrowIfNull(modelBuilder);
            foreach (var clrType in modelBuilder.Model.GetEntityTypes().Select(p => p.ClrType))
            {
                var properties = clrType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                    .Where(p => p.PropertyType == typeof(RowVersion));

                foreach (var property in properties)
                {
                    modelBuilder.Entity(clrType)
                        .Property(property.Name)
                        .IsConcurrencyToken().HasConversion(new ValueConverter<RowVersion, int>(
                            v => v.VersionNumber,
                            v => new RowVersion(v)));
                }
            }
        }

        /// <summary>
        /// 配置过滤器
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected virtual void ConfigureFilter(ModelBuilder modelBuilder)
        {
            var tenantId = _httpContextAccessor.HttpContext?.User?.FindFirst("tenant_id")?.Value;

            ArgumentNullException.ThrowIfNull(modelBuilder);
            foreach (var clrType in modelBuilder.Model.GetEntityTypes().Select(p => p.ClrType))
            {
                // 多租户过滤器
                if (typeof(ITenantEntity).IsAssignableFrom(clrType))
                {
                    modelBuilder.Entity<ITenantEntity>()
                        .HasQueryFilter(e => e.TenantId == tenantId);
                }
                if (typeof(ITenantEntity).IsAssignableFrom(clrType))
                {
                    modelBuilder.Entity<IDeleteEntity>()
                        .HasQueryFilter(e => !e.IsDelete);
                }
            }
        }


        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            ConfigureRowVersion(modelBuilder);
            ConfigureFilter(modelBuilder);
            base.OnModelCreating(modelBuilder);
        }

        protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
        {
            ConfigureStronglyTypedIdValueConverter(configurationBuilder);
            base.ConfigureConventions(configurationBuilder);
        }

        #region IUnitOfWork

        public IDbContextTransaction? CurrentTransaction { get; private set; }

        public IDbContextTransaction BeginTransaction()
        {
            if (_publisherTransactionFactory != null)
            {
                CurrentTransaction = _publisherTransactionFactory.BeginTransaction(this);
            }
            else
            {
                CurrentTransaction = Database.BeginTransaction();
            }

            return CurrentTransaction;
        }


        public async Task CommitAsync(CancellationToken cancellationToken = default)
        {
            if (CurrentTransaction != null)
            {
                await CurrentTransaction.CommitAsync(cancellationToken);
                CurrentTransaction = null;
            }
        }

        public async Task RollbackAsync(CancellationToken cancellationToken = default)
        {
            if (CurrentTransaction != null)
            {
                await CurrentTransaction.RollbackAsync(cancellationToken);
                CurrentTransaction = null;
            }
        }

        public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
        {
            if (CurrentTransaction == null)
            {
                CurrentTransaction = this.BeginTransaction();
                await using (CurrentTransaction)
                {
                    try
                    {
                        // ensure field 'Id' initialized when new entity added
                        await SaveChangesAsync(cancellationToken);
                        await _mediator.DispatchDomainEventsAsync(this, 0, cancellationToken);
                        await CommitAsync(cancellationToken);
                        return true;
                    }
                    catch
                    {
                        await RollbackAsync(cancellationToken);
                        throw;
                    }
                }
            }
            else
            {
                await SaveChangesAsync(cancellationToken);
                await _mediator.DispatchDomainEventsAsync(this, 0, cancellationToken);
                return true;
            }
        }

        #endregion

        #region SaveChangesAsync

        /// <summary>
        /// 
        /// </summary>
        /// <param name="changeTracker"></param>
        protected virtual void UpdateRowVersionBeforeSaveChanges(ChangeTracker changeTracker)
        {
            foreach (var entry in changeTracker.Entries())
            {
                if (entry.State == EntityState.Modified)
                {
                    var entityType = entry.Entity.GetType();
                    var properties = entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                        .Where(p => p.PropertyType == typeof(RowVersion));
                    foreach (var property in properties)
                    {
                        var rowVersion = (RowVersion)property.GetValue(entry.Entity)!;
                        var newRowVersion = new RowVersion(VersionNumber: rowVersion.VersionNumber + 1);
                        property.SetValue(entry.Entity, newRowVersion);
                    }
                }
            }
        }

        protected virtual void UpdateInitBaseEntitySaveChanges(ChangeTracker changeTracker)
        {
            var userId = "";
            var tenantId = "";
            var now = DateTime.Now;
            if (_httpContextAccessor != null)
            {
                userId = _httpContextAccessor.HttpContext?.User?.FindFirst("sub")?.Value;
                tenantId = _httpContextAccessor.HttpContext?.User?.FindFirst("tenant_id")?.Value;
            }


            foreach (var entry in changeTracker.Entries())
            {
                var entityType = entry.Entity.GetType();
                var properties = entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                foreach (var entity in properties)
                {
                    var delete = false;
                    if (entry.State == EntityState.Added)
                    {
                        SetPropertyValue(entity, nameof(Entity<object>.CreateTime), now);
                        SetPropertyValue(entity, nameof(Entity<object>.CreateById), userId);
                    }
                    else if (entry.State == EntityState.Modified)
                    {
                        SetPropertyValue(entity, nameof(Entity<object>.UpdateTime), now);
                        SetPropertyValue(entity, nameof(Entity<object>.UpdateById), userId);
                    }
                    else if (entry.State == EntityState.Deleted)
                    {
                        // 软删除
                        delete = true;
                        entry.State = EntityState.Modified; 
                        SetPropertyValue(entity, nameof(Entity<object>.DeleteTime), now);
                        SetPropertyValue(entity, nameof(Entity<object>.DeleteById), userId);
                    }

                    if (entity.PropertyType == typeof(ITenantEntity))
                    {
                        SetPropertyValue(entity, nameof(ITenantEntity.TenantId), now);
                    }
                    if (entity.PropertyType == typeof(IDeleteEntity))
                    {
                        SetPropertyValue(entity, nameof(IDeleteEntity.IsDelete), delete);
                    }
                }
            }
        }



        public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,
            CancellationToken cancellationToken = new CancellationToken())
        {
            UpdateRowVersionBeforeSaveChanges(ChangeTracker);
            UpdateInitBaseEntitySaveChanges(ChangeTracker);
            return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
        }

        #endregion


        private void SetPropertyValue(object entity, string propertyName, object value)
        {
            var property = entity.GetType().GetProperty(propertyName);
            if (property != null && property.CanWrite)
            {
                property.SetValue(entity, value);
            }
        }

    }
}

